﻿using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive.Linq;
using System.Windows;
using System.Windows.Threading;
using ZOIL.DataModel;
using ZOIL.Framework.Presentation;

namespace ZOIL.Library.Behaviors
{
    public delegate void ConnectionEventHandler(object sender, ConnectionEventArgs e);

    public class ConnectionBehavior : RuntimeBehavior<ZLandscapeCanvas, PluginConnectionBehavior>
    {
        #region private fields

        private readonly Dictionary<ILink, ShapeToShapeConnection> _establishedLinks;

        private IDisposable _elementAddedSubscription;
        private IDisposable _elementAddedWithLinkSubscription;
        private IDisposable _elementRemovedSubscription;

        #endregion

        #region dp

        protected override void OnIsEnabledChanged(bool oldIsEnabled, bool newIsEnabled)
        {
            ZComponent.IsComponentInteractionEnabled = !newIsEnabled;
            base.OnIsEnabledChanged(oldIsEnabled, newIsEnabled);
        }

        #region AreConnectionsEnabled

        /// <summary>
        /// AreConnectionsEnabled Dependency Property
        /// </summary>
        public static readonly DependencyProperty AreConnectionsEnabledProperty = DependencyProperty.Register("AreConnectionsEnabled", typeof(bool), typeof(ConnectionBehavior), new FrameworkPropertyMetadata(true, OnAreConnectionsEnabledChanged));

        /// <summary>
        /// Gets or sets the AreConnectionsEnabled property. This dependency property 
        /// indicates ....
        /// </summary>
        public bool AreConnectionsEnabled
        {
            get { return (bool)GetValue(AreConnectionsEnabledProperty); }
            set { SetValue(AreConnectionsEnabledProperty, value); }
        }

        /// <summary>
        /// Handles changes to the AreConnectionsEnabled property.
        /// </summary>
        private static void OnAreConnectionsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var target = (ConnectionBehavior)d;
            var oldAreConnectionsEnabled = (bool)e.OldValue;
            var newAreConnectionsEnabled = target.AreConnectionsEnabled;
            target.OnAreConnectionsEnabledChanged(oldAreConnectionsEnabled, newAreConnectionsEnabled);
        }

        /// <summary>
        /// Provides derived classes an opportunity to handle changes to the AreConnectionsEnabled property.
        /// </summary>
        protected virtual void OnAreConnectionsEnabledChanged(bool oldAreConnectionsEnabled, bool newAreConnectionsEnabled)
        {
        }

        #endregion

        #endregion

        #region ctor

        public ConnectionBehavior()
        {
            _establishedLinks = new Dictionary<ILink, ShapeToShapeConnection>(new DataModel.EqualityComparer<ILink>((l1, l2) => l1.Node1.DatabaseId == l2.Node1.DatabaseId && l1.Node2.DatabaseId == l2.Node2.DatabaseId, l => l.Node1.DatabaseId.GetHashCode() ^ l.Node2.DatabaseId.GetHashCode()));
        }

        #endregion

        #region Routed Events

        #region Connection Finished

        public static readonly RoutedEvent ConnectionFinishedEvent = EventManager.RegisterRoutedEvent("ConnectionFinished", RoutingStrategy.Bubble, typeof(ConnectionEventHandler), typeof(ConnectionBehavior));

        public event ConnectionEventHandler ConnectionFinished
        {
            add { AddRoutedEventHandler(ConnectionFinishedEvent, value); }
            remove { RemoveRoutedEventHandler(ConnectionFinishedEvent, value); }
        }

        #endregion

        #region Connection Started

        public static readonly RoutedEvent ConnectionStartedEvent = EventManager.RegisterRoutedEvent("ConnectionStarted", RoutingStrategy.Bubble, typeof(ConnectionEventHandler), typeof(ConnectionBehavior));

        public event ConnectionEventHandler ConnectionStarted
        {
            add { AddRoutedEventHandler(ConnectionStartedEvent, value); }
            remove { RemoveRoutedEventHandler(ConnectionStartedEvent, value); }
        }

        #endregion

        #region Connection Changed

        public static readonly RoutedEvent ConnectionChangedEvent = EventManager.RegisterRoutedEvent("ConnectionChanged", RoutingStrategy.Bubble, typeof(ConnectionEventHandler), typeof(ConnectionBehavior));

        public event ConnectionEventHandler ConnectionChanged
        {
            add { AddRoutedEventHandler(ConnectionChangedEvent, value); }
            remove { RemoveRoutedEventHandler(ConnectionChangedEvent, value); }
        }

        #endregion

        #region Connection Cancelled

        public static readonly RoutedEvent ConnectionCancelledEvent = EventManager.RegisterRoutedEvent("ConnectionCancelled", RoutingStrategy.Bubble, typeof(ConnectionEventHandler), typeof(ConnectionBehavior));

        public event ConnectionEventHandler ConnectionCancelled
        {
            add { AddRoutedEventHandler(ConnectionCancelledEvent, value); }
            remove { RemoveRoutedEventHandler(ConnectionCancelledEvent, value); }
        }

        #endregion

        #endregion

        #region event helper

        internal void RaiseConnectionStartedEvent(Connection connection)
        {
            var args = new ConnectionEventArgs(ConnectionStartedEvent, connection) { Source = this };
            AssociatedObject.RaiseEvent(args);
        }

        internal void RaiseConnectionChangedEvent(Connection connection)
        {
            var args = new ConnectionEventArgs(ConnectionChangedEvent, connection) { Source = this };
            AssociatedObject.RaiseEvent(args);
        }

        internal void RaiseConnectionCancelledEvent(Connection connection)
        {
            var args = new ConnectionEventArgs(ConnectionCancelledEvent, connection) { Source = this };
            AssociatedObject.RaiseEvent(args);
        }

        internal void RaiseCreatConnectionFinishedEvent(Connection connection)
        {
            var args = new ConnectionEventArgs(ConnectionFinishedEvent, connection) { Source = this };
            AssociatedObject.RaiseEvent(args);
        }

        #endregion

        #region overrides

        protected override void OnAttached()
        {
            base.OnAttached();

            _elementAddedWithLinkSubscription = 
                Observable.FromEventPattern<ZLandscapeCanvasEventArgs>(AssociatedObject, ZLandscapeCanvas.ElementAddedEvent.Name)
                .Where(evt => evt.EventArgs.Model is ILinkable)
                .Select(evt => Tuple.Create((ILinkable)evt.EventArgs.Model, evt.EventArgs.View))
                .Do(tuple =>
                {
                    var links = tuple.Item1.Links as INotifyCollectionChanged;

                    if (links != null)
                        links.CollectionChanged += LinksCollectionChanged;
                })
                .Where(tuple => tuple.Item1.Links.Any())
                .Subscribe(CreateLinks);

            var onElementRemoved = Observable.FromEventPattern<ZLandscapeCanvasEventArgs>(AssociatedObject, ZLandscapeCanvas.ElementRemovedEvent.Name).Do(evt => Console.WriteLine("Element Removed")).Where(evt => evt.EventArgs.Model is ILinkable).Select(evt => new Tuple<ILinkable, FrameworkElement>(evt.EventArgs.Model as ILinkable, evt.EventArgs.View)).Where(tuple => tuple.Item1.Links.Any());
            _elementRemovedSubscription = onElementRemoved.Subscribe(RemoveLinks);

            OnAttachedImpl();
        }

        protected override void OnDetaching()
        {
            OnDetachingImpl();

            if (_elementAddedSubscription != null)
                _elementAddedSubscription.Dispose();

            if (_elementAddedWithLinkSubscription != null)
                _elementAddedWithLinkSubscription.Dispose();

            if (_elementRemovedSubscription != null)
                _elementRemovedSubscription.Dispose();

            base.OnDetaching();
        }

        

        #endregion

        #region private methods

        private void OnAttachedImpl()
        {

        }

        private void OnDetachingImpl()
        {

        }

        private void LinksCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    foreach (var item in e.NewItems)
                    {
                        object item1 = item;
                        Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(() => CreateLink(item1 as ILink)));
//                        CreateLink(item as ILink);
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    break;
                default:
                    break;
            }
        }

        #endregion

        #region unknown

        private void RemoveLinks(Tuple<ILinkable, FrameworkElement> tuple)
        {
            var node = tuple.Item1;

            foreach (var link in node.Links)
            {
                if (_establishedLinks.ContainsKey(link))
                {
                    AssociatedObject.RemoveNonPersistableItem(_establishedLinks[link]);
                    _establishedLinks.Remove(link);
                }
            }
        }

        private void CreateLinks(Tuple<ILinkable, FrameworkElement> tuple)
        {
            var node = tuple.Item1;

            foreach (var link in node.Links)
                CreateLink(link);
        }

        private void CreateLink(ILink link)
        {
            if (!_establishedLinks.ContainsKey(link))
            {
                var node1 = link.Node1;
                while (node1 == null)
                {
                    node1 = link.Node1;
                }
                var node2 = link.Node2;
                while (node2 == null)
                {
                    node2 = link.Node2;
                }


                var shape1 = MVVMUtil.GetViewModel(node1) as IConnectionShape;
                var shape2 = MVVMUtil.GetViewModel(node2) as IConnectionShape;

                if (shape1 != null && shape2 != null)
                {
                    var vp = VisualProperties.Default();
                    vp.TemplateId = string.Format("{0}Template", link.GetType().Name);

                    var connection = new ShapeToShapeConnection(shape1, shape2, link);
                    _establishedLinks.Add(link, connection);
                    AssociatedObject.AddNonPersistableItem(connection, vp);
                }
            }
        }

        #endregion
    }
}
